/**
 * 检测模板模型
 *
 * Created by fangwenyu on 2021/03/08.
 */
const mongoose = require('mongoose');
const AnalyzeTemplate = require('./mongo/analyze-template');
const PhoenixError = require('./phoenix-error');
const ProjectVersion = require('./mongo/project-version');
const defects = require('./defects');
const RuleSet = require('./mongo/rule-set');
const Rule = require('./mongo/rule');
const ruleModel = require('./rules');
const utility = require('./utility');
const { eventHub, eventKind } = require('./event-hub');
const license = require('./license');
const logger = require('./logger');
const defectsClearedStatus = {
  none: 0,
  templateRulesChanged: 1,
  templateDeleted: 2,
};

const defaultTemplatePrefix = 'default';
const preferencesTemplatePrefix = 'Preferences';

async function createTemplate({
  creatorId,
  name,
  languages,
  rules,
  description = '',
  isDefault = false,
  userDefined,
} = {}) {
  creatorId = mongoose.Types.ObjectId(creatorId);
  languages = [].concat(languages);
  const template = new AnalyzeTemplate({
    creatorId,
    name,
    languages,
    rules: rules.map((item) => mongoose.Types.ObjectId(item)),
    description,
    isDefault,
    userDefined,
  });
  return template.save();
}

async function deleteTemplate(templateId, creatorId) {
  const tid = mongoose.Types.ObjectId(templateId);
  creatorId = mongoose.Types.ObjectId(creatorId);
  const template = await AnalyzeTemplate.findOne({
    _id: tid,
    creatorId,
  });
  if (!template) {
    throw new PhoenixError('not found', `template id ${templateId} not found`);
  }
  await defects.clearDefectsOfTemplate(templateId);
  await ProjectVersion.updateMany({ analyzeTemplate: tid }, {
    $set: {
      analyzeTemplate: null,
      'eventReminder.defectsCleared': defectsClearedStatus.templateDeleted,
    },
  });
  return template.remove();
}

async function checkDefaultTemplate(creatorId, prefix) {
  const templates = await AnalyzeTemplate.find({ creatorId });
  let defaultTemplatesExists = false;
  let defaultTemplateId = null;
  const nameSet = new Set();
  templates.forEach((template) => {
    nameSet.add(template.name);
    if (template.isDefault && template.name.startsWith(prefix)) {
      defaultTemplatesExists = true;
      defaultTemplateId = template._id;
    }
  });
  let defaultName = prefix;
  while (nameSet.has(defaultName)) {
    const [prefix, num] = defaultName.split('_');
    defaultName = `${prefix}_${num ? parseInt(num, 10) + 1 : 1}`;
  }
  return {
    defaultTemplatesExists, defaultName, defaultTemplateId,
  };
}

async function updateTemplate(templateId, doc, creatorId) {
  const tid = mongoose.Types.ObjectId(templateId);
  creatorId = mongoose.Types.ObjectId(creatorId);
  if (Object.hasOwnProperty.call(doc, 'name')) {
    const nameExists = await AnalyzeTemplate.findOne({
      name: doc.name, creatorId,
    });
    if (nameExists && !tid.equals(nameExists._id)) {
      throw new PhoenixError('conflict', `name '${doc.name}' already exists`);
    }
  }
  if (doc.rules || doc.userDefined) {
    const setVal = {};
    if (doc.rules) {
      setVal.rules = doc.rules;
    }
    const extraCfgKeys = new Set(Object.keys(ruleModel.ruleExtraConfig));
    if (doc.userDefined && utility.isArrayType(doc.userDefined) && doc.userDefined.length > 0) {
      doc.userDefined.forEach((item) => {
        const { ruleId, level, ...extra } = item;
        const ud = { level };
        setVal[`userDefined.${ruleId}`] = ud;
        Object.keys(extra).forEach((k) => {
          if (extraCfgKeys.has(k)) {
            ud[k] = extra[k];
          } else {
            logger.warn(`unknown field: ${k}`);
          }
        });
      });
    }
    const { n, nModified } = await AnalyzeTemplate.updateOne(
      { _id: tid, creatorId },
      { $set: setVal }
    );
    if (!n) {
      throw new PhoenixError('not found', `template id ${templateId} not found`);
    }
    if (nModified) {
      await defects.clearDefectsOfTemplate(templateId);
      await ProjectVersion.updateMany({ analyzeTemplate: tid }, {
        $set: { 'eventReminder.defectsCleared': defectsClearedStatus.templateRulesChanged },
      });
    }
  }
  delete doc.rules;
  delete doc.userDefined;
  const exist = await AnalyzeTemplate
    .findOneAndUpdate({ _id: tid, creatorId }, { $set: doc }, { new: true });
  return exist;
}
async function getPreferencesRules() {
  const licenseRuleSets = await license.getStaticAnalyzeRuleSetByLicense();
  let ruleSetInPreferences = [];
  const ruleSetOfPreferences = ['BugFinder', 'SJT11682-2017'];
  if (licenseRuleSets) {
    ruleSetInPreferences = ruleSetOfPreferences.filter((name) => licenseRuleSets.indexOf(name) >= 0);
  }
  if (!ruleSetInPreferences.length) {
    return {
      hasRules: false,
    };
  }
  const preferencesSetIds = await RuleSet.find({
    name: { $in: ruleSetInPreferences },
  }, { _id: 1, languages: 1 });
  const preferencesRules = (await Rule.find({
    ruleSetId: { $in: preferencesSetIds.map((document) => document._id) },
    inTemplate: true,
  }, { _id: 1 })).map((rule) => rule._id);
  return {
    hasRules: true,
    preferencesSetIds,
    preferencesRules,
  };
}
async function createPreferencesForUser(creatorId) {
  const templateInfo = await checkDefaultTemplate(creatorId, preferencesTemplatePrefix);
  if (templateInfo.defaultTemplatesExists) {
    return Promise.resolve();
  }
  const rulesInfo = await getPreferencesRules();
  if (!rulesInfo.hasRules) {
    return Promise.resolve();
  }
  const { preferencesSetIds, preferencesRules } = rulesInfo;
  try {
    await createTemplate({
      creatorId: mongoose.Types.ObjectId(creatorId),
      name: templateInfo.defaultName,
      languages: [...preferencesSetIds.reduce(
        (pre, ruleSet) => { ruleSet.languages.forEach((lang) => pre.add(lang)); return pre; },
        new Set()
      )],
      rules: preferencesRules,
      isDefault: true,
    });
  } catch (err) {
    if (err.code !== 11000) {
      throw err;
    }
  }
  return Promise.resolve();
}

async function updatePreferenceTemplate() {
  const preferencesTemplates = await AnalyzeTemplate.find({
    isDefault: true, name: { $regex: /^Preferences/ },
  });
  if (!preferencesTemplates.length) {
    return Promise.resolve();
  }
  const rulesInfo = await getPreferencesRules();
  let preferencesRules = [];
  if (rulesInfo.hasRules) {
    preferencesRules = rulesInfo.preferencesRules;
  }
  return Promise.all(preferencesTemplates.map((template) => updateTemplate(template._id, {
    rules: preferencesRules,
  }, template.creatorId)));
}

async function createDefaultTemplate(creatorId) {
  const defaultInfo = await checkDefaultTemplate(creatorId, defaultTemplatePrefix);
  if (defaultInfo.defaultTemplatesExists) {
    return Promise.resolve();
  }
  const { defaultName } = defaultInfo;
  const ruleSets = await RuleSet.find();
  try {
    const created = await createTemplate({
      creatorId: mongoose.Types.ObjectId(creatorId),
      name: defaultName,
      languages: [...ruleSets.reduce(
        (pre, ruleSet) => { ruleSet.languages.forEach((lang) => pre.add(lang)); return pre; },
        new Set()
      )],
      rules: [],
      isDefault: true,
    });
    return created;
  } catch (err) {
    if (err.code !== 11000) {
      throw err;
    }
  }
  return Promise.resolve();
}

async function getTemplates(creatorId, {
  page = 1,
  pageSize = 10,
  name,
  languages,
} = {}) {
  await createDefaultTemplate(creatorId);
  await createPreferencesForUser(creatorId);
  const filter = { creatorId };
  if (name) {
    filter.name = new RegExp(name);
  }
  if (languages && languages.length) {
    filter.languages = {
      $in: languages,
    };
  }
  const total = await AnalyzeTemplate.countDocuments(filter);
  const templates = (await AnalyzeTemplate.find(filter)
    .select('-rules')
    .sort({ isDefault: -1, _id: -1 })
    .skip((page - 1) * pageSize)
    .limit(pageSize))
    .map((template) => {
      template.languages.sort();
      return template;
    });

  return [templates, total];
}

/**
 * 获取检测模板信息
 * @param {String} templateId 模板Id
 * @return {Object}
 */
async function getTemplate(templateId, creatorId) {
  const tid = mongoose.Types.ObjectId(templateId);
  const condition = { _id: tid };
  if (creatorId) {
    condition.creatorId = mongoose.Types.ObjectId(creatorId);
  }
  const template = await AnalyzeTemplate.findOne(condition);
  if (template && template.isDefault && template.name.startsWith(defaultTemplatePrefix)) {
    const ruleSetId = await ruleModel.filterRuleSetByLicense();
    template.rules = (await Rule.find({ ruleSetId: { $in: ruleSetId } }, ['_id'])).map((rule) => rule._id);
  }
  return template;
}

eventHub.on(eventKind.EVENT_REMOVE_USER, (creatorId) => AnalyzeTemplate.deleteMany({ creatorId: mongoose.Types.ObjectId(creatorId) }));

module.exports = {
  createTemplate,
  createDefaultTemplate,
  deleteTemplate,
  getTemplates,
  updateTemplate,
  getTemplate,
  defectsClearedStatus,
  updatePreferenceTemplate,
  defaultTemplatePrefix,
};
exports = Object.assign(exports, module.exports);
